/**!
command bin
*/
use common::*;
use search::*;
use utils::*;
use web::*;

extern crate tokio;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

#[cfg(feature = "tracelog")]
use time::{format_description, UtcOffset};
#[cfg(feature = "tracelog")]
use tracing::{debug, error, event, info, instrument, Level};
#[cfg(feature = "tracelog")]
use tracing_subscriber::{
    self, filter::EnvFilter, fmt, fmt::time::OffsetTime, layer::SubscriberExt,
    util::SubscriberInitExt, Registry,
};

#[cfg(feature = "rotatelog")]
use log::{debug, error, info, trace, warn};
#[cfg(feature = "rotatelog")]
use log4rs;

use clap::{Parser, Subcommand};
use serde_json;

const DEFAULT_PORT: u16 = 9999;

#[derive(Parser, Debug)]
#[clap(
    name = "bull",
    version,
    author = "lengss",
    about = "textfull search with tantivy!"
)]

struct Cli {
    #[clap(subcommand)]
    command: Command,
    /// Sets a custom trace filter, such as info|debug|error|...
    #[clap(name = "trace", short, long, default_value = "info")]
    trace: String,
    /// Sets a custom config file
    #[clap(name = "config", short, long, default_value = "")]
    config: String,
}

#[derive(Subcommand, Debug)]
enum Command {
    Server {
        /// Listen port        
        #[clap(long, default_value_t = DEFAULT_PORT)]
        port: u16,
        /// Indexed data(code)'s path
        #[clap(long, default_value = "")]
        data: String,
    },
    ///  Publisher to send a message to a specific channel.
    Search {
        /// query string
        #[clap(long, default_value = "")]
        qry: String,
        /// query type: regex|regexf|fuzzy|fuzzyf|term|termf
        #[clap(long, default_value = "")]
        qtype: String,

        /// -author "search by author name"))
        #[clap(long, default_value_t = false)]
        author: bool,

        /// "search by file name"))
        #[clap(long, default_value_t = false)]
        fname: bool,

        /// search by modify time
        #[clap(long, default_value = "")]
        modtime: String,
    },
    /// Crawl a url page as text.
    Crawl {
        /// url page
        url: Option<String>,
    },
}

#[tokio::main]
async fn main() {
    // Parse command line arguments
    let cli = Cli::parse();
    
    #[cfg(feature = "tracelog")]
        tracelog_init(&cli.trace);
    
    #[cfg(feature="rotatelog")]
        rotatelog_init(&cli.trace);    

    match cli.command {
        Command::Crawl { url } => {
            if let Some(url) = url {
                if let Some(e) = utils::get_csdn(&url).err() {
                    // println!("crawle website error: {:#?}", e);
                    info!("crawle website error: {:#?}", e);
                }
            }
        }

        Command::Search {
            qry,
            qtype,
            author,
            fname,
            modtime,
        } => {
            search_request(&qry, &author, &fname, &qtype)
                .await
                .expect("error to request local server");
        }
        Command::Server { port, data } => {
            let file_path = if data.len() > 0 {
                data
            } else {
                String::from(".")
            };
            let nss = search::Nss::new(PathBuf::from("./"), "index", 5000000).unwrap();
            println!("run as a server");
            let cnss = Arc::new(nss);
            let web = tokio::spawn(search::shandong_main(Arc::clone(&cnss), file_path));
            let builder = tokio::spawn(web::beijing_main(Arc::clone(&cnss)));
            match tokio::try_join!(web, builder) {
                Err(err) => println!("processing failed; error = {:#?}", err),
                _ => {}
            }
        }
    }
    utils::color_print();
}

async fn search_request(
    qry: &str,
    author: &bool,
    fname: &bool,
    qtype: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let urls = format!(
        "http://localhost:9999/api/s?q={}&qtype={}&author={}&fname={}",
        qry, qtype, *author, *fname
    );
    // let resp = reqwest::blocking::get(urls)?
    let resp = reqwest::get(urls).await?.text().await?;

    let v: web::SearchResult = serde_json::from_str(&resp).unwrap();
    println!(
        "search: {}, counts = {}\n------------------------------",
        v.qry,
        v.results.len()
    );

    for (i, x) in v.results.iter().enumerate() {
        println!(
            "{}. [author]: {}, [filename]: {}\n{} ...\n",
            i, x.author, x.fname, x.content
        );
    }
    Ok(())
}

#[cfg(feature = "tracelog")]
fn tracelog_init(trace_level: &str) {
    let offset = if let Ok(offset) = UtcOffset::current_local_offset() {
        // println!("UtcOffset::current_local_offset() success!");
        offset
    } else {
        //let (h, m, s) = get_local_offset_hms();
        // println!("UtcOffset::current_local_offset() failed!, get offset from chrono ok");
        let dt = chrono::Local::now();
        let secs = dt.offset().local_minus_utc();
        UtcOffset::from_whole_seconds(secs).unwrap()
    };

    let env_filter =
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(trace_level));
    // 输出到控制台中
    let format = "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]";
    // 注册
    let formatting_layer = fmt::layer()
        .pretty()
        .with_writer(std::io::stderr)
        .with_timer(OffsetTime::new(
            offset,
            format_description::parse(format).unwrap(),
        ));

    Registry::default()
        .with(env_filter)
        .with(formatting_layer)
        .init();
}

#[cfg(feature = "rotatelog")]
fn rotatelog_init(log_yaml: &str) {
    if let Err(err) = log4rs::init_file(log_yaml, Default::default()) {
        println!("{:#?}", err);
    }
}
